﻿// File: LauncherForm.cs
// Project: SimplifyLauncher
// Solution: SimplifyLauncher
// Author: Sean Nelson

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace SimplifyLauncher
{
    /// <summary>
    /// Windows form for Simplify Launcher.
    /// Draws shortcuts given an arbitrary width/height, number of rows, and number of columns.
    /// Allows the user to add/remove/launch shortcuts to files and folders.
    /// A shortcut to a folder can do one of two things (chosen by the user):
    ///   1) Open the folder an explorer window
    ///   2) Add shortcuts for all files/folders in the folder and allow the user to navigate through them using the launcher
    /// Shortcut icons, their sizes, and their locations can be changed be the user.
    /// </summary>
    public partial class LauncherForm : Form
    {
        Launcher launcher;
        List<Rectangle> locations, locationAreas; // Lists of location rectangles and the areas around them
        Color backgroundColor, penColor; // Color used for the background when an image isn't chosen and pen color
        Pen penDotted, penSolid; // Pens used for dotted lined and solid lined location rectangles
        Shortcut shortcutDragged; // The shorcut currently being dragged
        SettingsForm settingsForm;
        AddShortcutForm addShortcutForm;

        int shortcutClickedIndex; // Set on the mouse down of a click (shortcut not clicked: -1, else: shortcut index)
        int lastShortcutClickedIndex; // Index of the last clicked shortcut (only -1 until a shortcut has been clicked)
        int shortcutDraggedIndex; // Index of the shortcut currently being dragged (no shortcut being dragged: -1, else: shortcut index)
        int locationHighlightedIndex; // Index of the currently highlighted location index (used when dragging shortcuts)
        bool locationsVisible; // If this is true, locations are drawn by the function 'LauncherForm_Paint'
        bool spacebarDown; // Whether or not the spacebar is currently being held down

        /// <summary>
        /// Constructor generates a launcher with user defined icon width, icon height, rows, and columns.
        /// </summary>
        public LauncherForm(Launcher launcher)
        {
            // Initialize colors
            backgroundColor = Color.Black;
            penColor = Color.White;

            // Create pens
            float[] dashes = { 1, 1 };
            penDotted = new Pen(penColor, 1);
            penDotted.DashPattern = dashes;
            penSolid = new Pen(penColor, 4);

            // Get icon width, icon height, rows, and columns defined in the constructor
            // Initialize corresponding global variables
            this.launcher = launcher;

            // Initialize other forms
            settingsForm = new SettingsForm(launcher);
            addShortcutForm = new AddShortcutForm(launcher, settingsForm);

            // Initialize all other global variables
            shortcutClickedIndex = -1;
            lastShortcutClickedIndex = -1;
            shortcutDraggedIndex = -1;
            locationHighlightedIndex = -1;
            locationsVisible = false;
            spacebarDown = false;

            // Generate shortcut locations according to shorcut/screen width and height
            // Initialize form
            GenerateLocations();
            InitializeComponent();

            // Prevent flickering on shortcut movement
            this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer, true);

            // Set up GUI handlers
            this.KeyDown += new KeyEventHandler(LauncherForm_KeyDown);
            this.KeyUp += new KeyEventHandler(LauncherForm_KeyUp);
            this.MouseMove += new MouseEventHandler(LauncherForm_MouseMove);
            this.MouseDown += new MouseEventHandler(LauncherForm_MouseDown);
            this.MouseUp += new MouseEventHandler(LauncherForm_MouseUp);
            this.Paint += new PaintEventHandler(LauncherForm_Paint);
        }

        /// <summary>
        /// Generates locations for shortcuts according to shorcut width/height, screen width/height, rows, and columns.
        /// Also generates a 'grid' of location areas around each location.
        /// Both locations and location areas are stored as rectangle objects (which include dimensions AND coordinates).
        /// 
        /// Location rectangles are:
        ///   - Drawn to the screen when the spacebar is pressed or when a shortcut is clicked and dragged
        ///   - Used by InsideShortcut to determine if the cursor is in a shortcut (and if so, returns the shortcut index)
        /// 
        /// Location area rectangles are:
        ///   - Used by InsideLocationArea to determine if the cursor is in the area around a location (and if so, returns the area index)
        ///  
        /// The rectangles for location area add a 'snap to nearest location' functionality when clicking/dragging a shortcut to a new location.
        /// </summary>
        private void GenerateLocations()
        {
            // Calculate gap sizes (distance between shortcuts and screen edges)
            int gapX = (launcher.ScreenWidth - (launcher.IconWidth * launcher.Columns)) / (launcher.Columns + 1);
            int gapY = (launcher.ScreenHeight - (launcher.IconHeight * launcher.Rows)) / (launcher.Rows + 1);

            // Initialize all other variables with empty lists and zeros
            locations = new List<Rectangle>();
            locationAreas = new List<Rectangle>();
            int x = 0;
            int y = 0;
            int areaX = 0;
            int areaY = 0;
            int areaWidth = 0;
            int areaHeight = 0;

            // Create all rectangles
            for (int i = 0; i < launcher.Columns; i++)
            {
                x += gapX;
                y = 0;
                for (int j = 0; j < launcher.Rows; j++)
                {
                    y += gapY;

                    // Create location rectangle and add to 'locations'
                    Rectangle location = new Rectangle(x, y, launcher.IconWidth, launcher.IconHeight);
                    locations.Add(location);

                    // Set default values for location area rectangle
                    areaX = x - gapX / 2;
                    areaY = y - gapY / 2;
                    areaWidth = launcher.IconWidth + gapX - 1;
                    areaHeight = launcher.IconHeight + gapY - 1;

                    // If location is on the left, expands its area to the left edge of the screen
                    if (x == gapX)
                    {
                        areaX = -1;
                        areaWidth += gapX / 2 + 1;
                    }

                    // If location is on the right, expands its area to the right edge of the screen
                    else if (x == launcher.ScreenWidth - gapX - launcher.IconWidth)
                        areaWidth += gapX / 2 + 1;

                    // If location is on the top, expands its area to the top edge of the screen
                    if (y == gapY)
                    {
                        areaY = -1;
                        areaHeight += gapY / 2 + 1;
                    }

                    // If location is on the bottom, expands its area to the bottom edge of the screen
                    else if (y == launcher.ScreenHeight - gapY - launcher.IconHeight)
                        areaHeight += gapY / 2 + 1;

                    // Create location area rectangle and add to 'locationAreas'
                    Rectangle locationArea = new Rectangle(areaX, areaY, areaWidth, areaHeight);
                    locationAreas.Add(locationArea);

                    y += launcher.IconHeight;
                }
                x += launcher.IconWidth;
            }
        }

        /// <summary>
        /// Adds the following keyboard down functionality:
        ///   - Esc closes the application
        ///   - Spacebar sets 'locationsVisible' to true
        /// </summary>
        private void LauncherForm_KeyDown(object sender, KeyEventArgs e)
        {
            // Esc
            if (e.KeyCode == Keys.Escape)
                this.Close();

            // Spacebar
            if (e.KeyCode == Keys.Space && !locationsVisible)
            {
                spacebarDown = true;
                locationsVisible = true;
                this.Refresh();
            }
        }

        /// <summary>
        /// Adds the following keyboard up functionality:
        ///   - Spacebar sets 'locationsVisible' to false
        /// </summary>
        private void LauncherForm_KeyUp(object sender, KeyEventArgs e)
        {
            // Spacebar
            if (e.KeyCode == Keys.Space && locationsVisible)
            {
                spacebarDown = false;
                locationsVisible = false;
                this.Refresh();
            }
        }

        /// <summary>
        /// Adds the following mouse move functionality:
        ///   When shortcut left clicked:
        ///     - Cursor changes to 'size all' if a shortcut was clicked
        ///     - Sets 'shortcutDraggedIndex' and 'shortcutDragged' (for drawing dragged shortcut later)
        ///     - Removes the shortcut from shortcuts dictionary (so it can be drawn in a new location later)
        ///     - Sets 'locationsVisible' to false
        ///   When left clicked and locations are visible:
        ///     - Sets 'locationHighlightedIndex' (for drawing highlights around locations later)
        ///   When nothing clicked:
        ///     - Cursor changes to hand when hovering over shortcuts
        /// </summary>
        private void LauncherForm_MouseMove(object sender, MouseEventArgs e)
        {
            if (!spacebarDown)
            {
                // Left Clicked
                if (e.Button == MouseButtons.Left)
                {
                    // Shorcut clicked
                    if (shortcutClickedIndex >= 0 && !locationsVisible)
                    {
                        Cursor = Cursors.SizeAll;

                        shortcutDraggedIndex = shortcutClickedIndex;
                        shortcutDragged = launcher.Shortcuts[shortcutDraggedIndex];
                        launcher.Shortcuts.Remove(shortcutDraggedIndex);

                        locationsVisible = true;
                        this.Refresh();
                    }
                    // Locations visible
                    if (locationsVisible)
                    {
                        locationHighlightedIndex = InsideLocationArea(e);
                        this.Refresh();
                    }
                }
                // Nothing clicked
                else if (e.Button == MouseButtons.None)
                {
                    if (InsideShortcut(e) >= 0)
                        Cursor = Cursors.Hand;
                    else
                        Cursor = Cursors.Default;
                }
            }
        }

        /// <summary>
        /// Adds the following mouse down functionality:
        ///   - Sets 'shortcutClicked' and 'lastShortcutIndex' if a shortcut was clicked (to store which shortcut is currently being worked with)
        /// </summary>
        private void LauncherForm_MouseDown(object sender, MouseEventArgs e)
        {
            int insideShortcut = InsideShortcut(e);

            // Shortcut clicked
            if (insideShortcut >= 0)
            {
                shortcutClickedIndex = insideShortcut;
                lastShortcutClickedIndex = shortcutClickedIndex;
            }
        }

        /// <summary>
        /// Adds the following mouse up functionality:
        ///   When shortcut clicked and not dragged:
        ///     - Launches shortcut if left clicked
        ///     - Shows shortcut menu if right clicked
        ///   When right clicked outside of a shortcut:
        ///     - Shows normal menu
        ///   When shortcut clicked and dragged:
        ///     - Adds the shortcut from shortcuts dictionary (so it can be drawn in its new location)
        ///     - Sets 'locationsVisible' to false
        ///   Always:
        ///     - Resets 'shortcutClickedIndex' and 'shortcutDraggedIndex' to -1
        /// </summary>
        private void LauncherForm_MouseUp(object sender, MouseEventArgs e)
        {
            if (!spacebarDown)
            {
                // Shortcut clicked and not dragged
                if (shortcutClickedIndex >= 0 && shortcutDraggedIndex == -1)
                {
                    if (e.Button == MouseButtons.Left)
                    {
// ADD CODE TO LAUNCH SHORTCUT HERE
                        MessageBox.Show("Left clicked inside shortcut!");
                    }
                    if (e.Button == MouseButtons.Right)
                        ShortcutRightClickMenu.Show(this, e.Location);
                }
                // Right clicked outside of shortcut
                else if (e.Button == MouseButtons.Right)
                     NormalRightClickMenu.Show(this, e.Location);

                // Shortcut clicked and dragged
                if (shortcutDraggedIndex >= 0)
                {
                    launcher.Shortcuts.Add(locationHighlightedIndex, shortcutDragged);
                    locationsVisible = false;
                    this.Refresh();
                }
            }
            shortcutClickedIndex = -1;
            shortcutDraggedIndex = -1;
        }

        /// <summary>
        /// Adds the following paint functionality:
        ///   Always:
        ///     - Draws all shortcuts from the dictionary 'shortcuts'
        ///   When 'locationsVisible' is true:
        ///     - Draws all locations
        ///     - Draws highlighted location according to 'locationHighlightedIndex'
        ///     - Draw the shortcut being dragged
        /// </summary>
        private void LauncherForm_Paint(object sender, PaintEventArgs e)
        {
            // Background color and image
            this.BackColor = backgroundColor;

            // All shortcuts
            foreach (KeyValuePair<int, Shortcut> shortcut in launcher.Shortcuts)
                e.Graphics.DrawImage(shortcut.Value.GetImage(), locations[shortcut.Key]);

            if (locationsVisible)
            {
                // All locations
                e.Graphics.DrawRectangles(penDotted, locations.ToArray());

                if (!spacebarDown)
                {
                    // Highlighted location
                    if (locationHighlightedIndex != -1)
                        e.Graphics.DrawRectangle(penSolid, locations[locationHighlightedIndex]);
                    // Dragged shortcut
                    e.Graphics.DrawImage(shortcutDragged.GetImage(), Cursor.Position.X - (launcher.IconWidth / 2), Cursor.Position.Y - (launcher.IconHeight / 2), launcher.IconWidth, launcher.IconHeight);
                }
            }
        }

        /// <summary>
        /// Adds functionality to 'Add Shortcut' in the normal menu.
        /// Opens the add shortcut form.
        /// </summary>
        private void AddShortcutMenuItem_Click(object sender, EventArgs e)
        {
            addShortcutForm.ShowDialog();
        }

        /// <summary>
        /// Adds functionality to 'Settings' in the normal menu.
        /// Opens the launcher settings form.
        /// </summary>
        private void SettingsMenuItem_Click(object sender, EventArgs e)
        {
            settingsForm.OpenTab("general");
        }

        /// <summary>
        /// Adds functionality to 'Exit' in the normal menu.
        /// Closes the application.
        /// </summary>
        private void ExitMenuItem_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        /// <summary>
        /// Adds functionality to 'Delete Shortcut' in the shortcut menu.
        /// Removes the shortcut from the dictionary 'shortcuts' and refreshes the LauncherForm (removing it from the GUI).
        /// </summary>
        private void DeleteShortcutMenuItem_Click(object sender, EventArgs e)
        {
            launcher.Shortcuts.Remove(lastShortcutClickedIndex);
            this.Refresh();
        }

        /// <summary>
        /// Determines whether or not the cursor is inside a shortcut.
        /// Returns the index of the shortcut else -1 if the mouse is not in a shortcut.
        /// Retrieves the mouse's current location using MousEventArgs 'e'.
        /// </summary>
        private int InsideShortcut(MouseEventArgs e)
        {
            int insideShortcut = -1;

            for (int i = 0; i < locations.Count; i++)
            {
                if (locations[i].Contains(e.Location) && launcher.Shortcuts.ContainsKey(i))
                {
                    insideShortcut = i;
                    break;
                }
            }

            return insideShortcut;
        }

        /// <summary>
        /// Determines whether or not the cursor is inside a location area.
        /// Returns the index of the area else -1 if the mouse is not in an area.
        /// Retrieves the mouse's current location using MousEventArgs 'e'.
        /// </summary>
        private int InsideLocationArea(MouseEventArgs e)
        {
            int insideLocationArea = -1;

            for (int i = 0; i < locationAreas.Count; i++)
            {
                if (locationAreas[i].Contains(e.Location))
                {
                    insideLocationArea = i;
                    break;
                }
            }

            return insideLocationArea;
        }
    }
}
